package other

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"

	"github.com/djimenez/iconv-go"
	"github.com/pkg/errors"

	"github.com/shopspring/decimal"
)

type Row struct {
	RealSize int   // 列的真实长度
	Cols     []Col // 列
}

type Col struct {
	// get from DB2
	ColNO   int    `json:"colno"`
	Name    string `json:"name"`
	ColType string `json:"coltype"`
	Length  int    `json:"length"`
	RealLen int    `json:"reallen"` // include null=="Y"(1byte) and varchar length(2byte) if col type is varchar,
	Scale   int    `json:"scale"`
	Nulls   string `json:"nulls"`
}

func (c *Col) Convert(inBuf []byte, from, to string) (string, error) {
	var outputStr string
	var err error
	var startPos int
	if c.Nulls == "Y" {
		startPos = 1 // 跳过允许为null字段的1个字节
	}
	switch c.ColType {
	case "CHAR", "CHARACTER", "DATE", "TIME", "TIMESTMP":
		outputStr, err = iconv.ConvertString(string(inBuf[startPos:]), from, to)
		if err != nil {
			return "", errors.Wrapf(err, "name:%s, [%x]", c.Name, inBuf)
		}
	case "VARCHAR":
		outputStr, err = iconv.ConvertString(string(inBuf[startPos+2:]), from, to)
		if err != nil {
			return "", errors.Wrapf(err, "name:%s, [%x]", c.Name, inBuf)
		}
	case "DECIMAL":
		if startPos == 1 && inBuf[0] == 0xff { // null
			outputStr, err = iconv.ConvertString(string(inBuf[startPos:]), from, to)
			if err != nil {
				return "", errors.Wrapf(err, "name:%s, [%x]", c.Name, inBuf)
			}
		} else {
			outputStr, err = Bytes2Decimal(inBuf[startPos:], c.RealLen-startPos, c.Scale)
			if err != nil {
				return "", errors.Wrapf(err, "name:%s, [%x]", c.Name, inBuf)
			}
		}

	case "INT", "INTEGER":
		var vv int32
		if err := binary.Read(bytes.NewReader(inBuf[startPos:]), binary.BigEndian, &vv); err != nil {
			return "", errors.Wrapf(err, "name:%s, %X", c.Name, inBuf)
		}
		outputStr = fmt.Sprintf("%d", vv)
	case "SMALLINT":
		var vv int16
		if err := binary.Read(bytes.NewReader(inBuf[startPos:]), binary.BigEndian, &vv); err != nil {
			return "", errors.Wrapf(err, "name:%s, %X", c.Name, inBuf)
		}
		outputStr = fmt.Sprintf("%d", vv)
	default:
		return "", errors.Errorf("name:%s, type:%s, not support data type", c.Name, c.ColType)
	}

	return outputStr, nil
}

func (c *Col) ConvertFixSize(input []byte, from, to string) ([]byte, error) {
	var startPos int
	var writeN = 0
	var err error
	if c.Nulls == "Y" {
		startPos = 1
	}

	outBuf := make([]byte, c.RealLen*3)
	switch c.ColType {
	case "CHAR", "CHARACTER":
		_, writeN, err = iconv.Convert(input, outBuf[startPos:], from, to)
		if err != nil {
			return outBuf, errors.Wrapf(err, "name:%s [%x]", c.Name, input)
		}
		// 不足补空格,而非0x00
		for i := startPos + writeN; i < c.RealLen; i++ {
			outBuf[i] = 0x40
		}
	case "VARCHAR":
		_, writeN, err = iconv.Convert(input, outBuf[startPos+2:], from, to)
		if err != nil {
			return outBuf, errors.Wrapf(err, "name:%s [%x]", c.Name, input)
		}

		// 不足补空格, 而非0x00
		for i := startPos + 2 + writeN; i < c.RealLen; i++ {
			outBuf[i] = 0x40
		}

		// 防止varchar长度超长
		if writeN > c.RealLen-2-startPos {
			writeN = c.RealLen - 2 - startPos
		}

		binary.BigEndian.PutUint16(outBuf[startPos:startPos+2], uint16(writeN))

	case "DECIMAL":
		if startPos == 1 && bytes.Count(input, []byte{0x20}) == len(input) { // 将空格替换为0x
			for i := 0; i < len(outBuf); i++ {
				outBuf[i] = 0x40
			}
		} else {
			outputByte, err := Decimal2Bytes(input, c.RealLen, c.Scale)
			if err != nil {
				return outBuf, errors.Wrapf(err, "name:%s, %x", c.Name, input)
			}
			copy(outBuf[startPos:], outputByte)
			writeN = 1 // set flag
		}

	case "INT", "INTEGER":
		// int32
		parseInt, err := strconv.ParseInt(string(input), 10, 32)
		if err != nil {
			return outBuf, errors.Wrapf(err, "name:%s [%x]", c.Name, input)
		}
		b := bytes.NewBuffer([]byte{})
		if err := binary.Write(b, binary.BigEndian, int32(parseInt)); err != nil {
			return outBuf, errors.Wrapf(err, "write name:%s [%x]", c.Name, input)
		}
		copy(outBuf[startPos:], b.Bytes())
		writeN = 1 // set flag
	case "SMALLINT":
		// int16
		parseInt, err := strconv.ParseInt(string(input), 10, 16)
		if err != nil {
			return outBuf, errors.Wrapf(err, "name:%s [%x]", c.Name, input)
		}
		b := bytes.NewBuffer([]byte{})
		if err := binary.Write(b, binary.BigEndian, int16(parseInt)); err != nil {
			return outBuf, errors.Wrapf(err, "write name:%s [%x]", c.Name, input)
		}
		copy(outBuf[startPos:], b.Bytes())
		writeN = 1 // set flag
	case "DATE", "TIME", "TIMESTMP":
		_, writeN, err = iconv.Convert(bytes.TrimRight(input, " "), outBuf[startPos:], from, to)
		if err != nil {
			return outBuf, errors.Wrapf(err, "name:%s [%x]", c.Name, input)
		}

		for i := startPos + writeN; i < c.RealLen; i++ {
			outBuf[i] = 0x40
		}

	default:
		return outBuf, errors.Errorf("name:%s, type:%s not support data type", c.Name, c.ColType)
	}

	// null字段且该字段转码为空
	if startPos == 1 && writeN == 0 {
		outBuf[0] = 0xff
	}

	return outBuf, nil
}

func Bytes2Decimal(in []byte, length int, scale int) (string, error) {
	hexRaw := hex.EncodeToString(in)
	newLen := length * 2
	n := newLen - scale - 1

	if len(hexRaw) < n || len(hexRaw) < newLen-1 {
		return "", errors.Errorf("hexRow len:%d, newLen:%d, input bytes not match length", len(hexRaw), newLen)
	}

	integerPart := hexRaw[:n]
	scalePart := hexRaw[n : newLen-1]

	fromString, err := decimal.NewFromString(strings.Join([]string{integerPart, scalePart}, "."))
	if err != nil {
		return fromString.String(), errors.WithStack(err)
	}

	// 正负号
	if in[length-1]&0x0F == 0x0D || in[length-1]&0x0F == 0x0B {
		return fromString.Mul(decimal.NewFromFloat(-1)).StringFixed(int32(scale)), nil
	} else {
		return fromString.StringFixed(int32(scale)), nil
	}
}

func Decimal2Bytes(in []byte, length, scale int) ([]byte, error) {
	// 去掉小数点，尾部补0占位，做符号占位
	newIn := bytes.ReplaceAll(in, []byte("."), []byte(""))
	dIndex := bytes.Index(in, []byte("."))
	scale = len(newIn) - dIndex

	out := make([]byte, length)
	if newIn[0] == 0x2d { // "-"
		newIn = newIn[1:]
		var ss string
		if len(newIn)%2 == 0 { // 偶数
			ss = fmt.Sprintf("0%sD", newIn)
		} else { // 奇数
			ss = fmt.Sprintf("%sD", newIn)
		}

		decode, err := hex.DecodeString(ss)
		if err != nil {
			return nil, errors.Wrapf(err, "newIn:%x", newIn)
		}

		if length > len(decode) { // 靠右侧填充
			copy(out[length-len(decode):], decode)
		} else {
			copy(out, decode)
		}
	} else { // "+"
		var ss string
		if len(newIn)%2 == 0 { // 偶数
			ss = fmt.Sprintf("0%sC", newIn)
		} else { // 奇数
			ss = fmt.Sprintf("%sC", newIn)
		}

		decode, err := hex.DecodeString(ss)
		if err != nil {
			return nil, errors.Wrapf(err, "newIn:%x", newIn)
		}

		if length > len(decode) { // 靠右侧填充
			copy(out[length-len(decode):], decode)
		} else {
			copy(out, decode)
		}
	}

	return out, nil
}
